#ifndef __MULTIPOPCORE_H__
#define __MULTIPOPCORE_H__

#include "SimpleCore.hpp"
#include <cstdlib>

/** \class MultiPopCoreException
 *  \brief Eccezioni lanciate dalla classe MultiPopCore
 */
class MultiPopCoreException : public GAcoreException
{
    public:
    MultiPopCoreException(char *mess) : GAcoreException(mess) {};
};


/** \class MultiPopCore
 *  \brief Core contenente piu' popolazioni indipendenti
 */
class MultiPopCore : public GAcore
{
protected:
    SimpleCore **myCores;
    //Validator  *myVal;

    int n_cores;

    enum IB_type_t {
        IB_TYPE_BW_REPLACE,     //!< Scambio dei peggiori BperCore * (num_cores-1) individui con i BperCore di ogni altro core
        IB_TYPE_NONE            //!< Nessuno scambio tra popolazioni
    };

    enum IB_timing_t {

        IB_TIMING_CONST,         //!< IB dopo un numero costante di generazioni
        IB_TIMING_NONE
    };

    IB_timing_t IB_timing;
    IB_type_t   IB_type;

    int BperCore;       //! Numero di individui presi per lo scambio da ogni core (usato se IB_type = IB_TYPE_BW_REPLACE)
    int numGens;        //! Numerod di generazioni tra le attivita' di IB (usato se IB_timing = IB_TIMING_CONST)

    void deleteCores();

    int check_IB_timing();
    void apply_IB();
    void IB_replace();

public:
    //TODO: I parametri specifici per un certo tipo di core vanno acquisiti con questo metodo o Ë meglio mettere un metodo apposta?
    //! Parametri calcolabili
    enum params {
        PARAM_ALL           = 0xFFFFFFFF,       //!< Calcola tutti i parametri

        PARAM_ALL_MEAN      = 0x00000200,       //!< Calcola la media del fitness per ogni popolazione
        PARAM_ALL_HAM_BEST  = 0x00000100,       //!< Mostra tutte le distanze medie dall'individuo migliore per ogni popolazione
        PARAM_ALL_B_SCORES  = 0x00000080,       //!< Ritorna tutti i punteggi migliori per ogni popolazione
        PARAM_NUM_CORES     = 0x00000040,       //!< Ritorna il numero di cores (?ma va messa qui?)

        PARAM_HAM_BEST      = 0x00000020,       //!< Calcola distanza di hamming dall'individuo migliore per tutte le popolazioni
        PARAM_N_CROSS       = 0x00000010,       //!< Calcola il numero di crossover (per generazione) nell'ultima chiamata a \c run_generation per tutte le popolazioni
        PARAM_N_MUT         = 0x00000008,       //!< Calcola il numero di mutazioni per individuo (per generazione) nell'ultima chiamata a \c run_generation per tutte le popolazioni
        PARAM_VAR_SCORE     = 0x00000004,       //!< Varianza del valore di fitness su tutte le popolazioni
        PARAM_MEAN_SCORE    = 0x00000002,       //!< Fitness medio di tutte le popolazioni
        PARAM_BEST_SCORE    = 0x00000001        //!< Best score di tutte le popolazioni
    };

    MultiPopCore(Validator *val,GAmodSettings &settings) throw (MultiPopCoreException);

    ~MultiPopCore();

    int loadSettings(GAmodSettings &settings,load_exc le = GAcore::THROW_EXCEPTION) throw(MultiPopCoreException);

    /** Restituisce informazioni sull'evoluzione
     *
     *  \param[out] settings    Contenitore delle statistiche calcolate
     *  \param[in] lista       Lista dei parametri da calcolare
     *  \return                 Numero di statistiche calcolate
     */
    int getGenerationData(GAmodSettings &settings,HERAuint32 lista = 0);

    /** Esegue l'evoluzione per il numero di generazioni specificato
     *
     *  \param[in]  num Numero di generazioni da eseguire
     *  \return         Numero di generazioni eseguite
     */
    int runGeneration(int num);

    void getPopolazione(Popolazione &p);
};

#endif
